package com.ysb.deros.processors;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.*;
import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import com.google.auto.service.AutoService;
import com.ysb.deors.annotations.BeanInfo;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;


@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.ysb.deors.annotations.BeanInfo"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class BeanInfoProcessor extends AbstractProcessor {

  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    String fqClassName = null;
    String className = null;
    String packageName = null;
    Map<String, VariableElement> fields = new HashMap<>();
    Map<String, ExecutableElement> methods = new HashMap<>();
    Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BeanInfo.class);

    if (elements.size() <= 0) {
      return false;
    }

    for (Element e : elements) {
      if (e.getKind() == ElementKind.CLASS) {
        TypeElement classElement = (TypeElement) e;
        PackageElement packageElement = (PackageElement) classElement.getEnclosingElement();

        processingEnv.getMessager().printMessage(
          Diagnostic.Kind.NOTE,
          "annotated class: " + classElement.getQualifiedName(), e);

        fqClassName = classElement.getQualifiedName().toString();
        className = classElement.getSimpleName().toString();
        packageName = packageElement.getQualifiedName().toString();

      } else if (e.getKind() == ElementKind.FIELD) {
        VariableElement varElement = (VariableElement) e;

        processingEnv.getMessager().printMessage(
          Diagnostic.Kind.NOTE,
          "annotated field: " + varElement.getSimpleName(), e);
        fields.put(varElement.getSimpleName().toString(), varElement);
      } else if (e.getKind() == ElementKind.METHOD) {
        ExecutableElement exeElement = (ExecutableElement) e;

        processingEnv.getMessager().printMessage(
          Diagnostic.Kind.NOTE,
          "annotated method: " + exeElement.getSimpleName(), e);

        methods.put(exeElement.getSimpleName().toString(), exeElement);
      }
     }

    Template vt = null;
    VelocityContext vc = new VelocityContext();

    if (fqClassName != null) {
      Properties props = new Properties();
      URL url = this.getClass().getClassLoader().getResource("velocity.properties");
      try {
        props.load(url.openStream());
      } catch (IOException e) {
        throw new RuntimeException(e);
      }

      VelocityEngine ve = new VelocityEngine(props);
      ve.init();

      vc.put("className", className);
      vc.put("packageName", packageName);
      vc.put("fields", fields);
      vc.put("methods", methods);

      vt = ve.getTemplate("beaninfo.vm");
    }

    JavaFileObject jfo = null;
    try {
      jfo = processingEnv.getFiler().createSourceFile(
        fqClassName + "BeanInfo");
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    processingEnv.getMessager().printMessage(
      Diagnostic.Kind.NOTE,
      "creating source file: " + jfo.toUri());

    Writer writer = null;
    try {
      writer = jfo.openWriter();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    processingEnv.getMessager().printMessage(
      Diagnostic.Kind.NOTE,
      "applying velocity template: " + vt.getName()
    );

    vt.merge(vc, writer);

    try {
      writer.close();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    return true;
  }
}
